fix(build): derive Astryx surface inventory from @astryxdesign/core - #3883
Conversation
5439e35 to
329a92b
Compare
Astro-Han
left a comment
There was a problem hiding this comment.
I reviewed this head and found no blocking issues.
Derives Astryx surface inventory from installed package .d.ts (fail-closed on unparsable, no silent fallback), with correct wrapper/re-implementation classification; hosted test/package/windows_recovery/audit/owner all SUCCESS.
No P0-P3.
简体中文
该头无阻断。Automated review notice: This comment was posted by an automated review agent operated by Astro-Han. It is not an independent human review and does not replace one.
329a92b to
47a2dc2
Compare
Astro-Han
left a comment
There was a problem hiding this comment.
I reviewed this head and found no blocking issues.
Drift from 5439e35 to 47a2dc2 is pure rebase (includes #3280); inventory derivation logic unchanged. Hosted test: FAILURE on e2e/workhub-layout is unrelated flake (Desktop E2E, not this PR's scope).
No P0-P3.
简体中文
该头为纯 rebase,无新增阻断。Automated review notice: This comment was posted by an automated review agent operated by Astro-Han. It is not an independent human review and does not replace one.
47a2dc2 to
78a6335
Compare
78a6335 to
1397483
Compare
1397483 to
ba1e061
Compare
Astro-Han
left a comment
There was a problem hiding this comment.
What this PR does
The generated docs/astryx-surface-file-inventory.md could not see most of the design system, for two reasons living in scripts/generate-astryx-surface-inventory.mjs: the known-component set was a hand-maintained 47-name list while @astryxdesign/core ships ~190 components (so Timestamp, ChatMessage, Thumbnail and friends appeared zero times across 213 rows even where they are imported and rendered), and severity only recognized raw button/input/select. This derives the component set by parsing the installed declarations — dist/index.d.ts plus each export * from './Dir' — and adds a reimplementation tier for a local component whose name matches a shipped Astryx twin it does not import.
Verdict
Comment review, not a block: 2 × P1, 2 × P2, 1 × P3, inline.
The diagnosis is right and the derivation half is well built — I checked the parser against the real 0.5.0 declarations and could not find a name it should have caught and missed (194 derived; export type blocks skipped, inline type modifiers handled, export interface XProps not mistaken for a component, SCREAMING constants and useX hooks correctly excluded). Output is byte-stable and locale-independent, and the version is exact-pinned in both manifests and the lockfile.
The two P1s are about the other half. One is mechanical: the gate became install-dependent without moving into the install condition, so an inventory-only PR now red-checks with "run npm install". The other is that the new reimplementation tier, on the current tree, produces two false positives and zero true positives — and one of them is exactly the "wrapper that renders the twin" shape the PR says it excludes.
The thread running through it
The PR names its own root cause well: "a generated artifact whose input is hand-maintained drifts in whichever half the author forgets." It then derives one of the two hand-maintained inputs and leaves the other. MAKA_UI_ASTRYX_REEXPORTS (:146) still hand-lists 9 names while packages/ui/src/index.ts re-exports 34, and the same loop at :281-291 now reads the derived set for one decision and the hand set for the other. So the drift is smaller but structurally identical, and 26 files still under-report.
The second thread is that "wrapper that renders the twin" was implemented as a same-file static-import check, which does not match how this repo actually writes wrappers — markdown.tsx reaches its twin through React.lazy across a file boundary. Widening the exclusion to follow a local re-export/lazy hop, or narrowing the tier to names that are unambiguous rather than generic, would both work; shipping it as-is puts wrong advice into an artifact CI enforces.
Mechanics
The conflict with main is only on the generated file (main deleted runtime-host-project-directory-editor.tsx; 214 rows there against 213 here). Rebase plus npm run astryx:surface-inventory:write resolves it — no hand-editing.
This does not make #2645 materially worse: it conflicts on the same file, its resolution is the same single regenerate command before and after, and it touches package-lock.json, so code=true and the P1 install bug does not hit it. It does mean #2645 can no longer resolve by hand-editing rows.
Note (ungraded)
The churn is understated by omission — the body lists the new names and tiers but not that 66 of 213 rows changed content. The stated totals (blocker 0, reimplementation 2, polish 1, aligned 210) reproduce exactly.
AI use: Claude Code assisted with running the generator, comparing the regenerated artifact, and reading the installed @astryxdesign/core declarations; both P1s were re-verified against the exact head by me, and the analysis, grading, and conclusions are my own. The worktree was restored after regeneration.
简体中文
这个 PR 在做什么:生成的 docs/astryx-surface-file-inventory.md 看不见设计系统的大部分,原因有两个都在生成脚本里——已知组件集是手工维护的 47 个名字,而 @astryxdesign/core 实际有约 190 个(于是 Timestamp、ChatMessage、Thumbnail 这些在 213 行里出现 0 次,哪怕文件真的 import 并渲染了它们);严重度又只认原生 button/input/select。本 PR 改为解析安装版声明文件(dist/index.d.ts 加每个 export * from './Dir')来推导组件集,并新增 reimplementation 档:本地组件名撞上已发布的 Astryx 同名件、且没有 import 它。
结论:comment review,不阻塞,2 × P1 / 2 × P2 / 1 × P3,全部在行内。
诊断是对的,推导那一半也做得扎实——我拿真实的 0.5.0 声明文件核了解析器,找不到应该收进来却漏掉的名字(推导出 194 个;export type 块正确跳过,行内 type 修饰符正确处理,export interface XProps 没被误判成组件,全大写常量和 useX hook 也都正确排除)。输出字节稳定、与 locale 无关,依赖在两个 manifest 和 lockfile 里都精确锁版。
两条 P1 都在另一半。一条是机械问题:这个门槛变成依赖安装了,却没有把 astryx_surface 移进 install 的条件,于是只改生成文件的 PR 现在会红,还配一句误导的 "run npm install"。另一条是新的 reimplementation 档在当前代码树上是 2 个误报、0 个真阳性——而其中一个恰恰就是 PR 自称会排除的"包了一层但渲染的还是同名件"。
贯穿其中的线索:PR 自己把根因写得很准——"输入靠手工维护的生成物,总会在作者忘记的那一半漂移"。但它只推导了两个手工输入中的一个。MAKA_UI_ASTRYX_REEXPORTS(:146)仍然手写 9 个名字,而 packages/ui/src/index.ts 实际再导出 34 个;:281-291 同一个循环里,一个判断读推导集、另一个读手工集。漂移变小了,结构完全一样,26 个文件仍在少报。第二条线索是"包一层就排除"被实现成了同文件静态 import 检查,而这个仓库真正的包装写法不是那样——markdown.tsx 是经 React.lazy 跨文件到达同名件的。
机械层面:与 main 的冲突只在生成文件上,rebase 后跑一次 npm run astryx:surface-inventory:write 即可,不需要手改。对 #2645 没有实质加重:同一个文件冲突,解法前后都是同一条重新生成命令,且它动了 package-lock.json,code=true,不会踩到上面那条 install 的坑;唯一影响是 #2645 不能再靠手改行来解冲突了。
不计分:改动量在描述里被省略了——正文列了新名字和新档位,但没提 213 行里有 66 行内容发生变化。给出的总计(blocker 0, reimplementation 2, polish 1, aligned 210)能精确复现。
f362f07 to
1182058
Compare
The file-level Astryx surface inventory could not see most of the design system. Its known-component set was a hand-maintained 47-name list while @astryxdesign/core ships ~190 components, so Timestamp, ChatMessage, Thumbnail and others were invisible — removing such a component from a file left its inventory row byte-identical and the coverage gate saw nothing. Severity also recognized only raw button/input/select, so a hand-rolled replacement for a shipped component scored aligned. Derive the component set by parsing the installed @astryxdesign/core declarations (hard error if the package is absent, so the artifact never silently regenerates with a partial set), add a reimplementation severity tier for a local component that shadows a shipped twin it does not import, and regenerate the committed inventory. Generated-by: Claude Opus 4.8 via Claude Code
1182058 to
9652360
Compare
Astro-Han
left a comment
There was a problem hiding this comment.
Thanks for replacing the hand-maintained Astryx surface map with data derived from the installed package. That is the right ownership boundary and materially reduces drift.
I reviewed exact head 96523606f62a36fe74be19186f369bf7daec3e87. I found one reachable P2 in a reasonable dependency-evolution path: the generator follows only one export * barrel layer. The “nested” fixture currently exercises a direct named export rather than a real Group -> Sub -> Component re-export chain. If Astryx later introduces a second barrel level, valid components can be silently omitted while the broad key/count thresholds still pass.
Could you either traverse the barrel graph recursively with cycle protection and fail-closed handling, or explicitly enforce and test that the installed Astryx package has a one-layer-only export contract? A regression fixture with a genuine multi-level barrel would make the intended boundary executable.
Separately, the current exact head has a failing test check and is conflicting with main, so it will need a refreshed head and green exact-head CI before merge. Those are delivery gates rather than additional code findings.
Review analysis was assisted by Codex and independent @reviewer agents; Astro-Han verified the exact-head implementation, package boundary, reachability, and CI state and owns this review.
中文对照
感谢你把手工维护的 Astryx surface 映射改为从已安装包中推导。这个职责边界是对的,也确实减少了漂移风险。
我审查了当前提交 96523606f62a36fe74be19186f369bf7daec3e87。这里有一个可达的 P2,属于合理的依赖包结构演进路径:生成器目前只会跟随一层 export * barrel。现有的 “nested” fixture 实际测试的是直接命名导出,而不是真正的 Group -> Sub -> Component 多层 re-export。如果 Astryx 以后增加第二层 barrel,合法组件可能会被静默遗漏,而宽泛的 key/数量阈值仍然可能通过。
建议二选一:递归遍历 barrel graph,并处理循环和严格失败;或者明确约束并测试当前安装的 Astryx 包只能有一层导出。最好补一个真正多层 barrel 的回归 fixture,把这个边界变成可执行契约。
另外,当前提交的 test 失败,并且与 main 有冲突;合并前需要刷新 head,并在该 exact head 上跑绿 CI。这些属于交付门槛,不是额外的代码问题。
本次分析由 Codex 和独立的 @reviewer agents 协助;Astro-Han 核验了当前提交的实现、包边界、可达性和 CI 状态,并对本次评审负责。
Summary
The file-level Astryx surface inventory (
docs/astryx-surface-file-inventory.md, generated byscripts/generate-astryx-surface-inventory.mjsand enforced byscripts/check-astryx-surface-inventory.mjs) could not see most of the design system, for two reasons — both in that one file:@astryxdesign/coreships ~194 components, soTimestamp,ChatMessage,Thumbnail, and others were invisible (0 rows) even thoughpackages/ui/src/chat-turn.tsxrenders them. Removing such a component left a row byte-identical, so the gate saw nothing (same root cause as Astryx surface inventory check is one-directional and not wired into CI #3064).button/input/select, so a hand-rolled twin of a shipped component scoredaligned.This PR:
.d.ts(dist/index.d.ts+ eachexport * from './Dir'). The derivation is fail-closed: an unresolved barrel target, a version that disagrees with the pinned dependency, or a parse below a floor / missing a key component aborts the run — never a silent partial set. The generated doc header records@astryxdesign/core@<version>and the export count.@maka/ui→ Astryx re-export map from the barrel export graph (packages/ui/src/index.ts, resolvingexport */ aliased re-export chains) instead of a hand-maintained list, so the "Astryx used" column is a true record.reimplementationtier: a public@maka/uiexport whose name shadows a shipped Astryx component but is defined locally (not a re-export). Neutral wording — a review signal, not a claim of semantic re-implementation. It flags 0 files on the current tree (no false positives).npm ciinstall step now runs onastryx_surface, so an inventory-doc-only PR installs dependencies before the generator runs; guarded by a newscripts/ci-test-plan.test.mjsassertion.Fixes #3868
Verification
Ran locally (Node 24,
@astryxdesign/core@0.5.0):npm run astryx:surface-inventory:write→astryx @0.5.0: blocker=0 reimplementation=0 polish=1 aligned=213npm run astryx:surface-inventory(coverage gate) →coverage: ok (214 files, 1 exclusions)npm run astryx:surface-inventory:test→ 9 pass (derivation version/floor/key-components; barrel re-export + nestedexport *+ local-shadow detection; fail-closed on unresolved target; severity cases)node --test scripts/ci-test-plan.test.mjs→ all pass (incl. the new install-dependencies-covers-astryx_surfaceassertion)biome checkon the changed scripts +package.json, andnode scripts/asf-license-headers.mjs check→ cleanNot run: full
npm run build/ repo-widetypecheck. The change is confined to build-tooling scripts and their generated docs (the.mjsscripts are outside the workspacetscgraph).Review response (thanks @codex)
npm cinow gates onastryx_surface; regression test added inci-test-plan.test.mjs.markdown.tsx'sMarkdownis not publicly exported, andmodule-hub-selector.tsx'sSelectoris private (the publicSelectoris a direct Astryx re-export). Now 0 findings, no false positives.@maka/uire-exports auto-derived: the hand-written list is removed; the map is resolved from the barrel export graph (35 names vs the old 9).typescript@7is the Go-native port — platform binaries, no classicts.createSourceFileJS API — so a hardened lightweight parser with explicit fail-closed checks is used instead.){ version, components }and the doc header states the version + export count.AI use
Tool(s) and scope: Claude Code (Claude Opus 4.8) — analysis, design of the derivation/detection, the script and test changes, and repository verification. The commit carries a
Generated-bytrailer.Checklist
Does this PR entail a change in behavior?